/**
 * Alibaba.com.cn Inc.
 * Copyright (c) 2004-2021 All Rights Reserved.
 */
package com.yongbao.common.extention;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.yongbao.common.extention.annotation.BizExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import static com.yongbao.common.extention.ExtensionConstants.ASTERISK;
import static com.yongbao.common.extention.ExtensionConstants.SEPARATOR;


/**
 * @author taixi
 * @version ExtensionManager.java, v 0.1 2020-03-16 10:47 taixi Exp $$
 */
public class ExtensionManager implements ApplicationListener<ContextRefreshedEvent> {

    /**
     * 日志
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(ExtensionManager.class);

    private static String IS_END = "isEnd";
    private static String EXTENSION_POINT = "ExtensionPoint";

    /**
     * processorMap
     */
    private static Map<String, Object> allExtensionMap = Maps.newHashMap();

    /**
     * processorMap
     */
    private static Map<String, Optional> extensionCacheMap = Maps.newHashMap();

    public static <T extends ExtensionPoint> T getExtension(String name) {
        return (T)getExtension(ExtensionPoint.class, name);
    }

    public static <T extends ExtensionPoint> T getExtension(Class<T> clazz, String name) {
        String cacheKey = clazz.getSimpleName() + ":" + name;
        //String cacheKey = name;
        Optional<ExtensionPoint> optional = extensionCacheMap.get(cacheKey);
        ExtensionPoint extensionPoint = null;
        if (optional == null || !optional.isPresent()) {
            extensionPoint = getExtensionInner(clazz, name);
            optional = Optional.ofNullable(extensionPoint);
            extensionCacheMap.put(cacheKey, optional);
        } else {
            extensionPoint = optional.get();
        }
        return (T)extensionPoint;
    }

    private static <T extends ExtensionPoint> T getExtensionInner(Class<T> clazz, String name) {
        if (CollectionUtils.isEmpty(allExtensionMap)) {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("extensionMap为空");
            }
            return null;
        }
        if (Objects.isNull(name)) {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("name为空，无法获取extension");
            }
            return null;
        }
        String[] array = name.split(SEPARATOR);
        int count = countAsterisk(array);
        if (count > 0) {
            LOGGER.info("参数错误，不能包含*号");
            return null;
        }

        List<ExtensionPoint> list = Lists.newArrayList();
        Map<String, Object> nowMap = (Map<String, Object>)allExtensionMap.get(clazz.getSimpleName());
        if (nowMap == null) {
            return null;
        }

        access(nowMap, array, 0, list);
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }

        return (T)list.get(0);

    }

    private static void access(Map<String, Object> nowMap, String[] array, int depth, List<ExtensionPoint> list) {
        String[] keys;
        if(array.length <= 1){
            keys = new String[] {array[depth]};
        }else{
            keys = new String[] {array[depth], ASTERISK};
        }

        for (String key : keys) {
            Map<String, Object> subMap = (Map<String, Object>)nowMap.get(key);
            if (subMap != null) {
                if (subMap.get(IS_END) != null && (Boolean)subMap.get(IS_END)) {
                    list.add((ExtensionPoint)subMap.get(EXTENSION_POINT));
                    return;
                } else {
                    access(subMap, array, depth + 1, list);
                }
            }
        }

    }

    private static int countAsterisk(String[] array) {
        return Arrays.stream(array).mapToInt(coordinate -> coordinate.contains(ASTERISK) ? 1 : 0).sum();
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        Map<String, Object> beans = contextRefreshedEvent.getApplicationContext().getBeansWithAnnotation(
                BizExtension.class);
        if (CollectionUtils.isEmpty(beans)) {
            return;
        }

        beans.entrySet().forEach(stringObjectEntry -> {
            ExtensionPoint extp = (ExtensionPoint)stringObjectEntry.getValue();
            BizExtension extensionAnnotation = extp.getClass().getAnnotation(BizExtension.class);
            String[] keyArr = extensionAnnotation.keys();
            Class group = extensionAnnotation.group();
            for (String name : keyArr) {
                if (StringUtils.isEmpty(name)) {
                    continue;
                }
                if (!check(name, extp)) {
                    //退出启动
                    System.exit(-1);
                }
                buildExtensionMap(group.getSimpleName(), name, extp);
            }
        });
    }

    public static void buildExtensionMap(String group, String name, ExtensionPoint extensionPoint) {
        Map<String, Object> subMap;
        Map<String, Object> nowMap = (Map<String, Object>)allExtensionMap.get(group);
        if (nowMap == null) {
            nowMap = Maps.newHashMap();
            allExtensionMap.put(group, nowMap);
        }

        String[] array = name.split(SEPARATOR);
        for (int i = 0; i < array.length; i++) {
            subMap = (Map<String, Object>)nowMap.get(array[i]);
            if (subMap == null) {
                subMap = Maps.newHashMap();
                nowMap.put(array[i], subMap);
            }
            nowMap = subMap;

            if (i == array.length - 1) {
                if (subMap.get(IS_END) != null) {
                    LOGGER.error("BizExtension使用有冲突。key={}", name, new Exception("BizExtension使用错误"));
                    System.exit(-1);
                }
                subMap.put(IS_END, true);
                subMap.put(EXTENSION_POINT, extensionPoint);
            }
        }
    }

    private static boolean check(String name, ExtensionPoint processor) {
        String[] array = name.split(SEPARATOR);
        long count = countAsterisk(array);
        if (count >= array.length) {
            LOGGER.error("BizExtension使用错误。key={} className={}", name, processor.getClass().getCanonicalName(),
                    new Exception("BizExtension使用错误"));
            return false;
        }
        return true;
    }

}
